package run.halo.app.service.assembler;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import run.halo.app.model.dto.SiteDetailDTO;
import run.halo.app.model.dto.SiteDTO;
import run.halo.app.model.entity.Category;
import run.halo.app.model.entity.Site;
import run.halo.app.model.entity.Tag;
import run.halo.app.model.vo.SiteDetailVO;
import run.halo.app.model.vo.SiteListVO;
import run.halo.app.service.CategoryService;
import run.halo.app.service.ContentService;
import run.halo.app.service.OptionService;
import run.halo.app.service.SiteCategoryService;
import run.halo.app.service.SiteTagService;
import run.halo.app.service.TagService;
import run.halo.app.utils.ServiceUtils;

/**
 * Site assembler.
 *
 * @author guqing
 * @date 2022-03-01
 */
@Component
public class SiteAssembler {

    private final SiteTagService siteTagService;

    private final SiteCategoryService siteCategoryService;

    private final TagService tagService;

    private final CategoryService categoryService;

    private final ContentService contentService;

    private final OptionService optionService;

    public SiteAssembler(ContentService contentService,
        OptionService optionService, SiteTagService siteTagService,
        SiteCategoryService siteCategoryService,
        TagService tagService,
        CategoryService categoryService) {
        this.siteTagService = siteTagService;
        this.siteCategoryService = siteCategoryService;
        this.tagService = tagService;
        this.categoryService = categoryService;
        this.contentService = contentService;
        this.optionService = optionService;
    }

    public SiteDetailDTO convertToDetailDTO(Site site) {
        Assert.notNull(site, "Site must not be null");
        return new SiteDetailDTO().convertFrom(site);
    }

    public List<SiteDetailDTO> convertToDetailDTO(List<Site> sites) {
        if (CollectionUtils.isEmpty(sites)) {
            return Collections.emptyList();
        }

        return sites.stream()
            .map(this::convertToDetailDTO)
            .collect(Collectors.toList());
    }

    /**
     * Converts to detail vo.
     *
     * @param site site must not be null
     * @return site detail vo
     */
    @NonNull
    public SiteDetailVO convertToDetailVo(Site site) {
        // List tags
        List<Tag> tags = siteTagService.listTagsBy(site.getId());
        // List categories
        List<Category> categories = siteCategoryService
            .listCategoriesBy(site.getId());
        // Convert to detail vo
        return convertTo(site, tags, categories);
    }

    /**
     * Converts to a page of detail vo.
     *
     * @param sitePage site page must not be null
     * @return a page of site detail vo
     */
    public Page<SiteDetailVO> convertToDetailVo(Page<Site> sitePage) {
        Assert.notNull(sitePage, "Site page must not be null");
        return sitePage.map(this::convertToDetailVo);
    }

    /**
     * Converts to a page of site list vo.
     *
     * @param sitePage site page must not be null
     * @return a page of site list vo
     */
    @NonNull
    public Page<SiteListVO> convertToListVo(Page<Site> sitePage) {
        Assert.notNull(sitePage, "Site page must not be null");

        List<Site> sites = sitePage.getContent();

        Set<Integer> siteIds = ServiceUtils.fetchProperty(sites, Site::getId);

        // Get tag list map
        Map<Integer, List<Tag>> tagListMap = siteTagService.listTagListMapBy(siteIds);

        // Get category list map
        Map<Integer, List<Category>> categoryListMap = siteCategoryService
            .listCategoryListMap(siteIds);

        return sitePage.map(site -> {
            SiteListVO siteListVO = new SiteListVO().convertFrom(site);

            Optional.ofNullable(tagListMap.get(site.getId())).orElseGet(LinkedList::new);

            // Set tags
            siteListVO.setTags(Optional.ofNullable(tagListMap.get(site.getId()))
                .orElseGet(LinkedList::new)
                .stream()
                .filter(Objects::nonNull)
                .map(tagService::convertTo)
                .collect(Collectors.toList()));

            // Set categories
            siteListVO.setCategories(Optional.ofNullable(categoryListMap.get(site.getId()))
                .orElseGet(LinkedList::new)
                .stream()
                .filter(Objects::nonNull)
                .map(categoryService::convertTo)
                .collect(Collectors.toList()));

            return siteListVO;
        });
    }

    public List<SiteListVO> convertToListVo(List<Site> sites) {
        Assert.notNull(sites, "Site page must not be null");

        Set<Integer> siteIds = ServiceUtils.fetchProperty(sites, Site::getId);

        // Get tag list map
        Map<Integer, List<Tag>> tagListMap = siteTagService.listTagListMapBy(siteIds);

        // Get category list map
        Map<Integer, List<Category>> categoryListMap = siteCategoryService
            .listCategoryListMap(siteIds);


        return sites.stream().map(site -> {
            SiteListVO siteListVO = new SiteListVO().convertFrom(site);

            Optional.ofNullable(tagListMap.get(site.getId())).orElseGet(LinkedList::new);

            // Set tags
            siteListVO.setTags(Optional.ofNullable(tagListMap.get(site.getId()))
                .orElseGet(LinkedList::new)
                .stream()
                .filter(Objects::nonNull)
                .map(tagService::convertTo)
                .collect(Collectors.toList()));

            // Set categories
            siteListVO.setCategories(Optional.ofNullable(categoryListMap.get(site.getId()))
                .orElseGet(LinkedList::new)
                .stream()
                .filter(Objects::nonNull)
                .map(categoryService::convertTo)
                .collect(Collectors.toList()));

            return siteListVO;
        }).collect(Collectors.toList());
    }


    /**
     * Converts to site detail vo.
     *
     * @param site site must not be null
     * @param tags tags
     * @param categories categories
     * @return site detail vo
     */
    @NonNull
    public SiteDetailVO convertTo(@NonNull Site site, @Nullable List<Tag> tags,
        @Nullable List<Category> categories) {
        Assert.notNull(site, "Site must not be null");

        // Convert to base detail vo
        SiteDetailVO siteDetailVO = new SiteDetailVO().convertFrom(site);
        // Extract ids
        Set<Integer> tagIds = ServiceUtils.fetchProperty(tags, Tag::getId);
        Set<Integer> categoryIds = ServiceUtils.fetchProperty(categories, Category::getId);

        // Get site tag ids
        siteDetailVO.setTagIds(tagIds);
        siteDetailVO.setTags(tagService.convertTo(tags));

        // Get site category ids
        siteDetailVO.setCategoryIds(categoryIds);
        siteDetailVO.setCategories(categoryService.convertTo(categories));

        return siteDetailVO;
    }

    public Page<SiteDTO> convertToDTO(Page<Site> sitePage) {
        return sitePage.map(this::convertToDTO);
    }

    @NonNull
    public SiteDTO convertToDTO(Site site) {
        Assert.notNull(site, "Site must not be null");
        SiteDTO dto = new SiteDTO().convertFrom(site);
        return dto;
    }

    public List<SiteDTO> convertToDTO(List<Site> siteList) {
        return siteList.stream().map(this::convertToDTO).collect(Collectors.toList());
    }
}
